/*
Copyright (C) 2007 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
 * @fileoverview Displays from Twitter the most recent messages from your
 * friends in the last 24 hours. This file handles mostly UI while twitter.js
 * handles the actual communication with Twitter.
 */

/**
 * The height (in pixels) of one message
 * @type Number
 */
var STATUS_DIV_HEIGHT = 39;
/**
 * Maximum number of messages visible
 * @type Number
 */
var MAX_MESSAGES = 20;
/**
 * Amount of time to wait until displaying 'timed out' message
 * @type Number
 */
var TIMEOUT_MS = 10000;  // 10 seconds
/**
 * Amount of time to wait until displaying 'Updating..." message
 * @type Number
 */
var SHOW_UPDATE_TIMEOUT_MS = 1000;  // 1 second
/**
 * How often to check for new messages, under normal circumstances
 * @type Number
 */
var BASE_UPDATE_INTERVAL_MS = 180000;  // 180 seconds
/**
 * How often to check for new messages, if we're offline
 * @type Number
 */
var CHECK_ONLINE_STATUS_INTERVAL_MS = 10000;  // 10 seconds
/**
 * The multiple representing no backoff.
 * @type Number
 */
var BASE_UPDATE_INTERVAL_MULTIPLE = 1;
/**
 * The maximum backoff multiple.
 * @type Number
 */
var MAX_UPDATE_INTERVAL_MULTIPLE = 60;
/**
 * How much we have backed off.
 * @type Number
 */
var g_updateIntervalMultiple = BASE_UPDATE_INTERVAL_MULTIPLE;
/**
 * Roughly how fast should we be backing off, if necessary.
 * @type Number
 */
var UPDATE_INTERVAL_BACKOFF_FACTOR = 2;
/**
 * Keeps track of the current updateFeed timer. Used to ensure that
 * we don't have multiple updateFeed timers at the same time.
 */
var g_updateFeedTimer = null;
/**
 * Maximum number of characters allowed in a message
 * @type Number
 */
var MAX_CHARS = 140;
/**
 * Twitter URL
 * @type String
 */
var TWITTER_URL = "http://twitter.com/";
/**
 * TwitterInfo object that holds all of the information for this user
 * @type TwitterInfo
 */
var twitterUserInfo;
/**
 * The username of the currently selected message (used for details view).
 * @type String
 */
var selectedUser;
/**
 * Map of message ID to div tag for all messages. This is used to easily remove
 * message div's once they are no longer displayed
 * @type Object
 */
var displayedMessagesMap;
/**
 * Flag if after sending a request to twiiter, we receive a response.
 * Used for determining timeouts.
 * @type Bool
 */
var requestReturned;
/**
 * Identifies that element has the focus. Used when a tab is pressed, we check
 * if the username field has focus and if so, move to the password field.
 * @type String
 */
var currentFocus;
/**
 * Used to determine if the details view is visible or not. This helps when a
 * user clicks an item for the second time, we close the details pane instead
 * of reopening it.
 * @type Number
 */
var currentItemDetailsView;
/**
 * Flag if we recieve a double click. This is used to supress the single click
 * function being called when we get a double click.
 * @type Bool
 */
var receivedDblclick;

/**
 * Message timeout ids. We track these so we can cancel them if the user signs
 * out.
 */
var loginTimeoutId;
var updatingTimeoutId;
var timedoutTimeoutId;

// Add the 'Refresh' button to the menu
plugin.onAddCustomMenuItems = onAddCustomMenuItems;

/**
 * Called when the gadget is created. Initializes all variables and displays
 * the login.
 */
function _onOpen() {
  debug.trace("_onOpen()");

  twitterUserInfo = new TwitterInfo();
  twitterUserInfo.max_id = 0;

  // view.onSize handler is not called when gadget starts up undocked.
  // This ensures sizing function is called.
  _onSize();

  contentViewport.enabled = true;
  contentViewport.onmousedown = onMouseDown;
  contentViewport.onmouseup = onMouseUp;
  contentViewport.onmousemove = onMouseMove;

  selectedUser = "";
  displayedMessagesMap = new Object();
  requestReturned = false;
  currentFocus = "";
  currentItemDetailsView = null;
  receivedDblclick = false;

  if (options.exists("username") && options.exists("password")) {
    debug.trace("have existing login values, logging in.");
    _onLogin(options.getValue("username"), options.getValue("password"));
  } else {
    showLogin();
  }

  resetBackoff();
  registerUpdateFeed();
}

/**
 * Removes all elements and show the login
 */
function showLogin() {
  content.removeAllElements();
  content.height = 0;
  loginContainer.visible = true;
  no_messages.visible = false;
  twitterUserInfo.isLoggedIn = false;

  // Empty all of the messages, if any
  if (twitterUserInfo.allMessages !== null) {
    while (twitterUserInfo.allMessages.length > 0) {
      twitterUserInfo.allMessages.pop();
    }
  }
}

/**
 * Get the entered username and password, remember it if user says so, 
 * and attempt to login
 */
function _onUserLogin() {
  if (save_credentials_checkbox.value) {
    options.putValue("username", username_box.value);
    options.putValue("password", password_box.value);
    options.encryptValue("password");
  }
  _onLogin(username_box.value, password_box.value);
}

/**
 * Attempt to login
 */
function _onLogin(username, password) {  
  // Set button to hover images
  signing_button_left.background = "signin_hover_left.png";
  signing_button_middle.background = "signin_hover_middle.png";
  signing_button_right.background = "signin_hover_right.png";

  // Trim the username and password of whitespaces
  var twitterUsername = username;
  if (twitterUsername.length > 0) {
    twitterUsername = twitterUsername.trim();
  } else {
    // Required to suppress windows authorization pop-up
    twitterUsername = " ";
  }
  twitterUserInfo.username = twitterUsername;

  var twitterPassword = password;
  if (twitterPassword.length > 0) {
    twitterPassword = twitterPassword.trim();
  } else {
    // Required to supress windows authorization pop-up
    twitterUserInfo.password = " ";
  }
  twitterUserInfo.password = twitterPassword;

  update_status.visible = true;
  requestReturned = false;
  loginTimeoutId = view.setTimeout("showTimeoutMessage('" + LOGGING_IN + "');",
                  SHOW_UPDATE_TIMEOUT_MS);
  // Call to twitter to check if the username and password are valid
  isLoggedIn(twitterUserInfo, loginResults);
}

/**
 * Callback function for isLoggedIn call. If the login is valid,
 * hide the login screen and call updateFeed to start getting the messages.
 * If it is false, display an error message.
 * @param {Bool} loginInfo true if the user is sucessfully logged in,
 *     false otherwise
 */
function loginResults(loginInfo) {
  if (loginInfo) {
    loginContainer.visible = false;
    update_status.visible = false;
    twitterUserInfo.isLoggedIn = true;
    requestReturned = true;
    updateFeed();
  } else {
    requestReturned = true;
    update_status.visible = true;
    update_status.innerText = USERNAME_PW;
    showLogin();
  }
}

/**
 * Resets the backoff
 */
function resetBackoff() {
  g_updateIntervalMultiple = BASE_UPDATE_INTERVAL_MULTIPLE;
}

/**
 * Decrease the rate of updateFeed requests, so we don't hose the servers.
 */
function backoff() {
  debug.trace("backing off");
  g_updateIntervalMultiple = Math.min(
    g_updateIntervalMultiple * UPDATE_INTERVAL_BACKOFF_FACTOR,
    MAX_UPDATE_INTERVAL_MULTIPLE);
}

/**
 * Sets a timeout for updateFeed to be called depending on the
 * online/offline state and the whether we're backing off
 */
function registerUpdateFeed() {
  var timeout;
  if (framework.system.network.online === false) {
    timeout = CHECK_ONLINE_STATUS_INTERVAL_MS;
  } else if (g_updateIntervalMultiple == BASE_UPDATE_INTERVAL_MULTIPLE) {
    timeout = BASE_UPDATE_INTERVAL_MS;
  } else {
    // timeout randomly in the later half of the interval
    var half_interval = BASE_UPDATE_INTERVAL_MS * g_updateIntervalMultiple / 2;
    timeout = half_interval + Math.random() * half_interval;
  }

  if (g_updateFeedTimer) {
    view.clearTimeout(g_updateFeedTimer);
    g_updateFeedTimer = null;
  }

  g_updateFeedTimer = view.setTimeout(updateFeed, timeout);
}

/**
 * Get the most recent messages from the users friends in the last 24 hours. If
 * There are more than MAX_MESSAGES (20) messages, only display
 * MAX_MESSAGES (20) messages.
 */
function updateFeed() {
  debug.trace("updating feed");
  if (twitterUserInfo.isLoggedIn) {
    update_status.innerText = "";

    // Remove messages that are no longer visible
    removeOldMessages();

    update_status.visible = true;
    requestReturned = false;
    if (framework.system.network.online === false) {
      showTimeoutMessage(OFFLINE);
    } else {
      updatingTimeoutId = view.setTimeout("showTimeoutMessage('" + UPDATING + "');",
        SHOW_UPDATE_TIMEOUT_MS);
      timedoutTimeoutId = view.setTimeout(
        "showTimeoutMessage('" + TIMED_OUT + "');", TIMEOUT_MS);
      getLatestMessages(twitterUserInfo, MAX_MESSAGES, updateFeedCallback);
    }
  }

  // make sure updateFeed gets called again
  registerUpdateFeed();
}

/**
 * Called once we get a response from Twitter while trying to update the list
 * of messages. Takes in the statusCode of the twitter response and the number
 * of new messages that we found (if any). If we get a successful response from
 * Twitter we call updateDisplay to show the new messages, otherwise display
 * an appropriate error.
 * @param {Number} statusCode The status returned by twitter
 * @param {Number} numNewMessages Number of new messages received from Twitter
 */
function updateFeedCallback(statusCode, numNewMessages) {
  requestReturned = true;
  update_status.visible = true;
  if (statusCode == 200) {
    updateDisplay(numNewMessages);
    resetBackoff();
  } else {
    updateStatusFromCode(statusCode);
  }
}

/**
 * Get the message that was typed in and attempt to update the user's message.
 */
function _updateMessage() {
  if (message_box.value.length <= MAX_CHARS) {
    text_remaining.innerText = MAX_CHARS;
    update_status.visible = true;
    requestReturned = false;
    timedoutTimeoutId = view.setTimeout(
      "showTimeoutMessage('" + TIMED_OUT + "');", TIMEOUT_MS);
    updateTwitterMessage(twitterUserInfo,
                         message_box.value,
                         updateMessageCallback);
    message_box.value = "";
  } else {
    var message = Utils_substitutePH(strings.MUST_BE_LESS,
        strings.MUST_BE_LESS_PH, MAX_CHARS);
    view.alert(message);
  }
}

/**
 * Called once we get a response from Twitter while trying to update the user's
 * message.
 * @param {Number} statusCode The status returned by twitter
 */
function updateMessageCallback(statusCode) {
  requestReturned = true;
  update_status.visible = true;

  if (statusCode == 200) {
    updateFeed();
  } else {
    updateStatusFromCode(statusCode);
  }
}

/**
 * Checks the status code and displays an appropriate error message
 * @param {Number} statusCode The status returned by twitter
 */
function updateStatusFromCode(statusCode) {
  // this function gets called when a non-200 (essentially an error)
  // was returned
  backoff();
  if (statusCode == 304) {
    // HTTP Status: 304 Not Modified
    if (twitterUserInfo.allMessages.length === 0) {
      no_messages.visible = true;
    } else {
      no_messages.visible = false;
    }
    update_status.visible = false;
  } else if (statusCode == 401) {
    // HTTP Status: 401 Unauthorized
    update_status.innerText = AUTH_ERROR;
    showLogin();
  } else if (statusCode == 404) {
    // HTTP Status: 404 Forbidden
    update_status.innerText = CONNECTING_ERROR;
  } else if (statusCode >= 500) {
    // All other connection errors
    update_status.innerText = CONNECTING_ERROR;
  }
}

var g_lastScrollY = 0;
var g_isScrolling = false;

function onMouseDown() {
  g_isScrolling = true;
  g_lastScrollY = event.y;
}

function onMouseUp() {
  g_isScrolling = false;
  g_lastScrollY = 0;
}

function onMouseMove() {
  if (g_isScrolling) {
    var newY = event.y;
    var delta = newY - g_lastScrollY;
    g_lastScrollY = newY;

    var newContentY = content.y + delta;
    var minY = contentViewport.height - content.height;

    if (newContentY > 0) {
      newContentY = 0;
    } else if (newContentY < minY) {
      newContentY = minY;
    }

    content.y = newContentY;
  }
}

/**
 * Takes in the number of new messages and creates each message UI. Once all
 * of the messages have been created it animates them so they scroll down from
 * the top.
 * @param {Number} numnNewMessages Number of new messages received from Twitter
 */
function updateDisplay(numNewMessages) {
    var newMessage = null;
    update_status.innerText = "";
    if (numNewMessages > 0) {
      for (var i = 0; i < numNewMessages; ++i) {
        newMessage = content.appendElement(
            '<div name="message_' + twitterUserInfo.allMessages[i].id +
            '" y="0" ' +
            'enabled="false" visible="false" ' + 'width="100%" height="' +
            STATUS_DIV_HEIGHT + '" background="#E0FF92"></div>');
        content.height += STATUS_DIV_HEIGHT;

        var element = newMessage.appendElement('<img  width="32" ' + 
          'height="32" y="4" name="image" />');
        setStatusImage(twitterUserInfo.allMessages[i].imageurl, element);
        newMessage.appendElement(
            '<label name="username" x="35" y="0" font="Arial" size="8" ' +
            'color="#52991f">'+twitterUserInfo.allMessages[i].name+'</label>');

        var contentLabel = newMessage.appendElement(
            '<label name="message_content" x="35" y="12" height="25" ' +
            'wordWrap="true" font="Arial" size="8" ' + 
            'trimming="word-ellipsis" />');

        contentLabel.innerText = twitterUserInfo.allMessages[i].text;
        contentLabel.tooltip = twitterUserInfo.allMessages[i].text;
        contentLabel.enabled = true;

        contentLabel.onclick = function(message) {
          return function() {
            singleClick(message);
          };
        } (twitterUserInfo.allMessages[i].id);

        contentLabel.ondblclick = function(message) {
          return function() {
            openItemInBrowser(message);
          };
        } (twitterUserInfo.allMessages[i].id);

        contentLabel.cursor = 'hand';

        newMessage.appendElement('<div y="39" height="1" width="100%" '+
                                 'background="#FFFFFF"></div>');
        displayedMessagesMap[twitterUserInfo.allMessages[i].id] = newMessage;
      }

      // Cancels scrolling state.
      onMouseUp();
      content.y = 0;

      var shiftAmount = STATUS_DIV_HEIGHT * numNewMessages;
      view.beginAnimation("shiftDown(" + numNewMessages + ");",
                          0,
                          shiftAmount,
                          1500);
    }

    updateContentWidth();

    if (twitterUserInfo.allMessages.length === 0) {
      no_messages.visible = true;
    } else {
      no_messages.visible = false;
    }
}

/**
 * Removes all messages other than the MAX_MESSAGES (20) most recent messages
 */
function removeOldMessages() {
  if (twitterUserInfo.allMessages.length > MAX_MESSAGES) {
    while (twitterUserInfo.allMessages.length > (MAX_MESSAGES)) {
      var tempMessage = twitterUserInfo.allMessages.pop();
      if (displayedMessagesMap[tempMessage.id] !== null) {
        content.removeElement(getDivById(tempMessage.id));
        content.height -= STATUS_DIV_HEIGHT;
        displayedMessagesMap[tempMessage.id] = null;
      }
    }

    // Keep on the last MAX_MESSAGES (20) messages
    twitterUserInfo.allMessages =
      twitterUserInfo.allMessages.slice(0, MAX_MESSAGES);
  }
}

/**
 * Called when a user presses any key
 */
function _onKeyPressed() {
  if (currentFocus == "password_box" && event.keycode == 13) {
    // If the focus is on the password box and the user presses enter, login.
    _onUserLogin();
  } else if (currentFocus == "message_box" && event.keycode == 13) {
    // If the focus is on the message box and the user presses enter, update
    // the users status.
    _updateMessage();
  } else if (currentFocus == "message_box") {
    // Determine how many characters are left. The problem here is that the text
    // hasn't been entered into message_field at this point so we need to test
    // if they are adding a character or removing one (by detecting the
    // backspace button) and set the numRemaining accordingly.
    var numRemaining = MAX_CHARS - message_box.value.length;
    if (event.keycode == 8) {
      numRemaining = numRemaining + 1;
    }

    text_remaining.innerText = numRemaining.toString();
    // Display a warning if the user's message is longer than MAX_CHARS
    // characters.
    if (numRemaining < 0) {
      update_status.innerText = TOO_LONG;
      update_status.visible = true;
    } else {
      update_status.visible = false;
    }
  }
}

/**
 * Called when the user hovers over the sign-in button, replaces to image with
 * the hover image.
 */
function _onSigninHover() {
  signing_button_left.background = "signin_hover_left.png";
  signing_button_middle.background = "signin_hover_middle.png";
  signing_button_right.background = "signin_hover_right.png";
}

/**
 * Called when the user moves the mouse off of sign-in button, replaces to image
 * with the default image.
 */
function _onSigninOut() {
  signing_button_left.background = "signin_default_left.png";
  signing_button_middle.background = "signin_default_middle.png";
  signing_button_right.background = "signin_default_right.png";
}

/**
 * Called when the user moves clicks on the sign-in button, replaces to image
 * with the down image.
 */
function _onSigninDown() {
  signing_button_left.background = "signin_down_left.png";
  signing_button_middle.background = "signin_down_middle.png";
  signing_button_right.background = "signin_down_right.png";
}

/**
 * If the focus is on the password field and space is pressed, update 
 * the checkbox. If tab is pressed, go to the signin field.
 */
function _checkboxKeyPressed() {
  if (event.keycode == 32) {
    save_credentials_checkbox.value = !save_credentials_checkbox.value;
  }
  event.returnValue = false;
}

/**
 * On enter signin.
 * On tab return to the user name field.
 */

function _signingButtonKeyPressed() {
  if (event.keycode == 13) {
    _onUserLogin();
  }
  event.returnValue = false;
}
/**
 * Open a url in an external web browser
 */
function _openSite(url) {
  if (url !== null) {
    framework.openUrl(url);
  }
}

/**
 * Assign the focus to the name of the element
 */
function _onFocusIn(name) {
  currentFocus = name;
}

/**
 * Remove the focus from the current element
 */
function _onFocusOut(name) {
  currentFocus = "";
}

/**
 * Add refresh button to the menu
 */
function onAddCustomMenuItems(menu) {
  menu.AddItem("Refresh", 0, updateFeed);
  menu.AddItem(strings.SIGNOUT, 0, onSignOut);
}

/**
 * Sign out of twitter
 */ 
function onSignOut() {
  // clear the storage
  options.remove("username");
  options.remove("password");
  
  // clear the text boxes
  username_box.value = "";
  password_box.value = "";
  
  // cancel timers that might display stuff
  view.clearTimeout(loginTimeoutId);
  view.clearTimeout(updatingTimeoutId);
  view.clearTimeout(timedoutTimeoutId);
  
  _onOpen();
}

/**
 * Given a message ID, returns the DIV associated with this ID.
 * @return {Object} The div associated with the message ID
 */
function getDivById(theId) {
  return content.children.item("message_" + theId);
}

/**
 * Animation function that shifts down all messages that need to be seen and
 * hides the messages that are being overwritten (messages that occur after
 * the MAX_MESSAGES (20th) message)
 * @param {Number} numNewMessages the number of new messages to be shown
 */
function shiftDown(numNewMessages) {
  for (var i = 0; i < twitterUserInfo.allMessages.length; ++i) {
    var tempMessage = getDivById(twitterUserInfo.allMessages[i].id);
    if (i < MAX_MESSAGES) {
      if (!tempMessage.visible) {
        tempMessage.visible = true;
      }
      tempMessage.y = (i - numNewMessages) * STATUS_DIV_HEIGHT + event.value;
    } else {
      tempMessage.visible = false;
    }
  }
}

/**
 * Display a message in the status area above the message test box.
 * Generally used when a timeout has occured.
 * @param {String} message the message to display in the status area
 */
function showTimeoutMessage(message) {
  if (!requestReturned) {
    update_status.innerText = message;
  }
}

/**
 * Goes through all visible messages and adjust the width of the message.
 * Called as the gadget is being resized.
 */
function updateContentWidth() {
  var theMaximum = twitterUserInfo.allMessages.length;
  if ( twitterUserInfo.allMessages.length > MAX_MESSAGES ) {
    theMaximum = MAX_MESSAGES;
  }
  for (var i = 0; i < theMaximum; ++i) {
    var the_message = getDivById(twitterUserInfo.allMessages[i].id);
    the_message.children("message_content").width = view.width - 60;
  }
}

/**
 * Takes in a message ID and opens the message in an external browser
 * @param {Number} theItemId the message id of the item to be displayed
 */
function openItemInBrowser(theItemId) {
  receivedDblclick = true;
  var curItem = getMessageById(theItemId);
  if (currentItemDetailsView != -1) {
    plugin.CloseDetailsView();
    currentItemDetailsView = -1;
  }
  _openSite(TWITTER_URL+curItem.name+"/statuses/"+theItemId);
}

/**
 * Display the details for this selected message.
 * @param {Number} theItemId the message id of the item to be displayed
 */
function showDetailsView(theItemId) {
  if (!receivedDblclick) {
    if (theItemId == currentItemDetailsView) {
      plugin.CloseDetailsView();
      currentItemDetailsView = -1;
      return;
    }

    var curItem = getMessageById(theItemId);
    selectedUser = curItem.name;

    var htmlDetailsView = new DetailsView();
    htmlDetailsView.html_content = false;
    htmlDetailsView.setContent("", undefined, curItem.text, false, 0);

    // Show the details view.
    pluginHelper.showDetailsView(htmlDetailsView,
                                 curItem.name,
                                 gddDetailsViewFlagToolbarOpen,
                                 onDetailsViewFeedback);

    currentItemDetailsView = theItemId;
  }
}

/**
 * Display the details for this selected message.
 * @param {Number} detailsViewFlags flags for the details view
 */
function onDetailsViewFeedback(detailsViewFlags) {
  if (detailsViewFlags == gddDetailsViewFlagNone) {
    currentItemDetailsView = -1;
  } else if (detailsViewFlags == gddDetailsViewFlagToolbarOpen) {
    if (selectedUser !== null) {
       _openSite(TWITTER_URL+selectedUser);
    }
  }
}

/**
 * Given an ID it returns the entire message object
 * @param {Number} theId the message ID of the item to retrieve
 * @return {TwitterMessage} the message
 */
function getMessageById(theId) {
  for (var i = 0; i < twitterUserInfo.allMessages.length; ++i) {
    if (twitterUserInfo.allMessages[i].id == theId) {
      return twitterUserInfo.allMessages[i];
    }
  }
}

/**
 * Called when the user single clicks on an active element
 * @param {Number} theId the message id of the item that was clicked on
 */
function singleClick(theItemId) {
  receivedDblclick = false;
  view.setTimeout(function() { showDetailsView(theItemId); }, 200);
}

/**
 * Underlines the username on the message
 * @param {Number} theId the message id of the item that was clicked on
 */
function highlightItem(itemId) {
  var tempItem = content.children.item("message_"+itemId);
  tempItem.children.item("username").underline = true;
}

/**
 * Removes the underline on the username on the message
 * @param {Number} theId the message id of the item that was clicked on
 */
function unhighlightItem(itemId) {
  var tempItem = content.children.item("message_"+itemId);
  tempItem.children.item("username").underline = false;
}

/**
 * Sets the size of the gadget and handles boundry conditions. If the width
 * goes below 80 or the height goes below 135, we stop typing to resize and
 * just let the gadget get cut off.
 */
function _onSize() {
  debug.trace("_onSize()");

  // The minimum height of the gadget where content should start being cut off
  var MIN_HEIGHT = 77;
  // The minimum width of the gadget where content should start being cut off
  var MIN_WIDTH = 100;
  // The minimum width of the content (where the messages appear)
  var MIN_CONTENT_WIDTH = 80;
  // The width of the images on the right side of the gadget
  var IMAGES_RIGHT_WIDTH = 95;
  // The width of the left and right images combined
  var IMAGES_RIGHT_AND_LEFT_WIDTH = 135;
  // the height of the images on the bottom of the gadget
  var IMAGES_BOTTOM_HEIGHT = 43;
  // UI elements offsets

  var MESSAGE_BOX_WIDTH_OFFSET = 45;
  var MESSAGE_BOX_Y_OFFSET = 27;
  var SUBMIT_BUTTON_OFFSET = 32;
  var SUBMIT_BUTTON_OFFSET_Y = 29;
  var STATUS_MESSAGE_Y_OFFSET = 40;
  var CONTENT_HEIGHT_OFFSET = 79;
  var CONTENT_WIDTH_OFFSET = 27;

  var LOGIN_HEIGHT_OFFSET = 50;
  var LOGIN_FIELD_WIDTH_OFFSET = 38;
  var LOGIN_BUTTON_X_OFFSET = 23;
  var SIGNIN_BUTTON_WIDTH_OFFSET = 43;
  var SIGNIN_LABEL_X_OFFSET = 25;

  // Always update the width of the messages
  updateContentWidth();

  if (view.height < MIN_HEIGHT && view.width >= MIN_WIDTH) {
    // Handle the case where the gadget is less than the minimum height.
    // Resize the background images.
    top_right.x = view.width - IMAGES_RIGHT_WIDTH;
    top_middle.width = view.width - IMAGES_RIGHT_AND_LEFT_WIDTH;
    middle_middle.width = view.width - IMAGES_RIGHT_WIDTH;
    middle_right.x = view.width - IMAGES_RIGHT_WIDTH;
    bottom_middle.width = view.width - IMAGES_RIGHT_AND_LEFT_WIDTH;
    bottom_right.x = view.width - IMAGES_RIGHT_WIDTH;

    // Resize the UI elements (non-background images)
    message_box.width = view.width - MESSAGE_BOX_WIDTH_OFFSET;
    submit_button.x = view.width - SUBMIT_BUTTON_OFFSET;
  } else if (view.width < MIN_WIDTH && view.height >= MIN_HEIGHT) {
    // Handle the case where the gadget is less than the minimum width
    // Resize the background images.
    middle_left.height = view.height - MIN_HEIGHT;
    middle_middle.height = view.height - MIN_HEIGHT;
    middle_right.height = view.height - MIN_HEIGHT;
    bottom_left.y = view.height - IMAGES_BOTTOM_HEIGHT;
    bottom_middle.y = view.height - IMAGES_BOTTOM_HEIGHT;
    bottom_right.y = view.height - IMAGES_BOTTOM_HEIGHT;

    // Resize the UI elements (non-background images)
    text_remaining.y = view.height - STATUS_MESSAGE_Y_OFFSET;
    update_status.y = view.height - STATUS_MESSAGE_Y_OFFSET;
    message_box.y = view.height - MESSAGE_BOX_Y_OFFSET;
    submit_button.y = view.height - SUBMIT_BUTTON_OFFSET_Y;
    contentViewport.height = view.height - CONTENT_HEIGHT_OFFSET;
    loginContainer.height = view.height - LOGIN_HEIGHT_OFFSET;
    no_messages.height = view.height - CONTENT_HEIGHT_OFFSET;
  } else if (view.height >= MIN_HEIGHT && view.width >= MIN_WIDTH) {
    // Handle the normal case where the gadget larger than the minimum size in
    // both the width and the height.
    // Resize the background images.
    top_right.x = view.width - IMAGES_RIGHT_WIDTH;
    top_middle.width = view.width - IMAGES_RIGHT_AND_LEFT_WIDTH;
    middle_left.height = view.height - MIN_HEIGHT;
    middle_middle.width = view.width - IMAGES_RIGHT_WIDTH;
    middle_middle.height = view.height - MIN_HEIGHT;
    middle_right.x = view.width - IMAGES_RIGHT_WIDTH;
    middle_right.height = view.height - MIN_HEIGHT;
    bottom_left.y = view.height - IMAGES_BOTTOM_HEIGHT;
    bottom_middle.y = view.height - IMAGES_BOTTOM_HEIGHT;
    bottom_middle.width = view.width - IMAGES_RIGHT_AND_LEFT_WIDTH;
    bottom_right.x = view.width - IMAGES_RIGHT_WIDTH;
    bottom_right.y = view.height - IMAGES_BOTTOM_HEIGHT;

    // Resize the UI elements (non-background images)
    content.width = view.width - CONTENT_WIDTH_OFFSET;
    contentViewport.width = view.width - CONTENT_WIDTH_OFFSET;
    contentViewport.height = view.height - CONTENT_HEIGHT_OFFSET;
    loginContainer.height = view.height - LOGIN_HEIGHT_OFFSET;
    no_messages.width = view.width - CONTENT_WIDTH_OFFSET;
    no_messages.height = view.height - CONTENT_HEIGHT_OFFSET;
    text_remaining.y = view.height - STATUS_MESSAGE_Y_OFFSET;
    update_status.y = view.height - STATUS_MESSAGE_Y_OFFSET;
    message_box.width = view.width - MESSAGE_BOX_WIDTH_OFFSET;
    message_box.y = view.height - MESSAGE_BOX_Y_OFFSET;
    submit_button.x = view.width - SUBMIT_BUTTON_OFFSET;
    submit_button.y = view.height - SUBMIT_BUTTON_OFFSET_Y;

    // Resize the login information UI
    username_background_middle.width = view.width - LOGIN_FIELD_WIDTH_OFFSET;
    username_background_right.x = view.width - LOGIN_BUTTON_X_OFFSET;
    password_background_middle.width = view.width - LOGIN_FIELD_WIDTH_OFFSET;
    password_background_right.x = view.width - LOGIN_BUTTON_X_OFFSET;
    username_box.width = view.width - MESSAGE_BOX_WIDTH_OFFSET;
    password_box.width = view.width - MESSAGE_BOX_WIDTH_OFFSET;
    signing_button_middle.width = view.width - SIGNIN_BUTTON_WIDTH_OFFSET;
    signing_button_right.x = view.width - LOGIN_BUTTON_X_OFFSET;
    signin_label.x = view.width / 2 - SIGNIN_LABEL_X_OFFSET;
  }
}
